/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace DaveSexton.DocProject
{
	public sealed partial class ExternalSourcesEditorDialog : Form
	{
		#region Public Properties
		public bool HasChanges
		{
			get
			{
				return hasChanges;
			}
		}

		public IEnumerable<Source> Sources
		{
			get
			{
				List<Source> sources = new List<Source>();

				foreach (DataGridViewRow row in sourcesDataGridView.Rows)
				{
					if (row.IsNewRow)
						continue;

					string source = (string) row.Cells[sourceColumn.DisplayIndex].Value;
					string xmlDocumentation = (string) row.Cells[xmlDocumentationColumn.DisplayIndex].Value;

					SystemPath sourcePath, xmlDocumentationPath;

					if (string.IsNullOrEmpty(source))
						sourcePath = SystemPath.Empty;
					else
						sourcePath = new SystemPath(project.Directory, source, true);

					if (string.IsNullOrEmpty(xmlDocumentation))
						xmlDocumentationPath = SystemPath.Empty;
					else
						xmlDocumentationPath = new SystemPath(project.Directory, xmlDocumentation, true);

					if (sourcePath != SystemPath.Empty || xmlDocumentationPath != SystemPath.Empty)
						sources.Add(new Source(sourcePath, xmlDocumentationPath));
				}

				return sources.AsReadOnly();
			}
		}

		public bool UseAssemblyFilter
		{
			get
			{
				return useAssemblyFilter;
			}
			set
			{
				useAssemblyFilter = value;

				SynchronizeUIForSourceFilter();
			}
		}

		public string CustomSourceFilter
		{
			get
			{
				return customFilter;
			}
			set
			{
				customFilter = ((string.IsNullOrEmpty(value)) ? null : value);

				SynchronizeUIForSourceFilter();
			}
		}

		public IEnumerable<string> UniqueSourceExtensions
		{
			get
			{
				return (useAssemblyFilter)
					? PathFormat.GetUniqueDialogFilterExtensions(StringComparison.OrdinalIgnoreCase, customFilter, true, ".dll", ".exe")
					: PathFormat.GetUniqueDialogFilterExtensions(StringComparison.OrdinalIgnoreCase, customFilter, false);
			}
		}

		public IEnumerable<string> UniqueSourceFilters
		{
			get
			{
				return (useAssemblyFilter)
					? PathFormat.GetUniqueDialogFilters(StringComparison.OrdinalIgnoreCase, customFilter, true, "*.exe", "*.dll")
					: PathFormat.GetUniqueDialogFilters(StringComparison.OrdinalIgnoreCase, customFilter, false);
			}
		}
		#endregion

		#region Private / Protected
		private bool hasChanges, useAssemblyFilter = true;
		private string customFilter, sourceFilter = Resources.Text.AssemblyDialogFilter;
		private readonly IDocProject project;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="ExternalSourcesEditorDialog" /> class.
		/// </summary>
		public ExternalSourcesEditorDialog(IEnumerable<Source> sources, IDocProject project)
			: this()
		{
			if (project == null)
				throw new ArgumentNullException("project");

			this.project = project;

			this.Text = project.Name + " - " + this.Text;

			foreach (Source source in sources)
				sourcesDataGridView.Rows.Add(source.Input.CompleteRelativeTo(project.Directory), source.XmlDocumentation.CompleteRelativeTo(project.Directory));

			sourceColumn.InitializeFileDialog += fileColumn_InitializeFileDialog;
			sourceColumn.InitializeFolderDialog += fileColumn_InitializeFolderDialog;

			xmlDocumentationColumn.InitializeFileDialog += fileColumn_InitializeFileDialog;
			xmlDocumentationColumn.InitializeFolderDialog += fileColumn_InitializeFolderDialog;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="ExternalSourcesEditorDialog" /> class.
		/// </summary>
		private ExternalSourcesEditorDialog()
		{
			InitializeComponent();
		}
		#endregion

		#region Methods
		private void SynchronizeUIForSourceFilter()
		{
			if (useAssemblyFilter)
			{
				sourceColumn.HeaderText = Resources.Text.ExternalSourcesSourceColumnHeaderText;
				sourceFilter = Resources.Text.AssemblyDialogFilter;

				if (!string.IsNullOrEmpty(customFilter))
				{
					sourceFilter = PathFormat.InsertAllTypesDialogFilter(
						Resources.Text.AllAssembliesAndFilesDialogFilterPrefix,
						sourceFilter + "|" + customFilter);
				}
			}
			else
			{
				sourceColumn.HeaderText = Resources.Text.ExternalSourcesFileHeaderText;
				sourceFilter = customFilter;
			}

			if (string.IsNullOrEmpty(sourceFilter))
				sourceFilter = Resources.Text.AllFilesDialogFilter;
		}

		private void FindRelatedXmlDocumentationFile(int rowIndex, string source)
		{
			DataGridViewRow row = sourcesDataGridView.Rows[rowIndex];

			string path;
			if (System.IO.Path.IsPathRooted(source))
				path = source;
			else
				path = System.IO.Path.Combine(project.Directory, source);

			if (PathFormat.IsDirectory(path))
				row.Cells[xmlDocumentationColumn.DisplayIndex].Value = path;
			else
			{
				string directory = System.IO.Path.GetDirectoryName(path);
				string baseName = System.IO.Path.GetFileNameWithoutExtension(path);

				row.Cells[xmlDocumentationColumn.DisplayIndex].Value = 
					(PathFormat.HasWildcard(baseName))
					? System.IO.Path.Combine(directory, "*.xml")
					: System.IO.Path.Combine(directory, baseName + ".xml");
			}
		}

		private void FindRelatedSource(int rowIndex, string xmlDocumentation)
		{
			string path;
			if (System.IO.Path.IsPathRooted(xmlDocumentation))
				path = xmlDocumentation;
			else
				path = System.IO.Path.Combine(project.Directory, xmlDocumentation);

			DataGridViewRow row = sourcesDataGridView.Rows[rowIndex];

			if (PathFormat.IsDirectory(path))
			{
				foreach (string extension in UniqueSourceExtensions)
				{
					if (System.IO.Directory.GetFiles(path, "*" + extension, System.IO.SearchOption.TopDirectoryOnly).Length > 0)
					{
						row.Cells[sourceColumn.DisplayIndex].Value = path;
						break;
					}
				}
			}
			else
			{
				string directory = System.IO.Path.GetDirectoryName(path);
				string baseName = System.IO.Path.GetFileNameWithoutExtension(path);
				string source = System.IO.Path.Combine(directory, baseName);

				foreach (string extension in UniqueSourceExtensions)
				{
					string file = source + extension;

					if (System.IO.File.Exists(file))
					{
						row.Cells[sourceColumn.DisplayIndex].Value = file;
						break;
					}
				}
			}
		}

		private void FindRelatedValue(int rowIndex, int columnIndex)
		{
			if (rowIndex == -1)
				return;

			DataGridViewRow row = sourcesDataGridView.Rows[rowIndex];
			DataGridViewCell cell = row.Cells[columnIndex];
			string value = ((string) cell.Value ?? "").Trim();

			if (!row.IsNewRow)
				hasChanges = true;

			if (value.Length > 0)
			{
				if (columnIndex == sourceColumn.DisplayIndex)
				{
					string currentXmlDocumentationFile = (string) row.Cells[xmlDocumentationColumn.DisplayIndex].Value;

					if (string.IsNullOrEmpty(currentXmlDocumentationFile))
						FindRelatedXmlDocumentationFile(rowIndex, value);
				}
				else if (columnIndex == xmlDocumentationColumn.DisplayIndex)
				{
					string currentSource = (string) row.Cells[sourceColumn.DisplayIndex].Value;

					if (string.IsNullOrEmpty(currentSource))
						FindRelatedSource(rowIndex, value);
				}
			}
		}

		private bool ValidateCell(DataGridViewCell cell)
		{
			string value = (string) cell.FormattedValue;

			if (string.IsNullOrEmpty(value) || !PathFormat.IsValid(value, false))
			{
				errors.SetError(okButton, Resources.Text.ExternalSourcesInvalidPath);
				return false;
			}

			return true;
		}
		#endregion

		#region Event Handlers
		protected override void OnValidating(CancelEventArgs e)
		{
			foreach (DataGridViewRow row in sourcesDataGridView.Rows)
			{
				if (!ValidateCell(row.Cells[sourceColumn.DisplayIndex]))
					e.Cancel = true;

				if (!ValidateCell(row.Cells[xmlDocumentationColumn.DisplayIndex]))
					e.Cancel = true;
			}

			if (!e.Cancel)
				errors.SetError(okButton, null);

			base.OnValidating(e);
		}

		private void fileColumn_InitializeFileDialog(object sender, DataGridViewFileColumnDialogEventArgs e)
		{
			e.Dialog.CheckFileExists = true;

			if (e.ColumnIndex == sourceColumn.DisplayIndex)
			{
				e.Dialog.Filter = sourceFilter;
				e.Dialog.Title = "Select a source";
			}
			else
			{
				e.Dialog.Filter = Resources.Text.XmlFileDialogFilter;
				e.Dialog.Title = "Select an XML documentation file";
			}
		}

		private void fileColumn_InitializeFolderDialog(object sender, DataGridViewFileColumnFolderDialogEventArgs e)
		{
			if (e.ColumnIndex == sourceColumn.DisplayIndex)
				e.Dialog.Description = "Select a source folder";
			else
				e.Dialog.Description = "Select an XML documentation source folder";
		}

		private void okButton_Click(object sender, EventArgs e)
		{
			if (Validate(false))
				DialogResult = DialogResult.OK;
		}

		private void sourcesDataGridView_UserAddedRow(object sender, DataGridViewRowEventArgs e)
		{
			hasChanges = true;
		}

		private void sourcesDataGridView_UserDeletedRow(object sender, DataGridViewRowEventArgs e)
		{
			hasChanges = true;
		}

		private void sourcesDataGridView_CellEnter(object sender, DataGridViewCellEventArgs e)
		{
			// HACK: This event handler is a workaround for an EditOnEnter bug that prevents rows from being selected when a header is clicked.
			// This code is based on the workaround found here: https://connect.microsoft.com/VisualStudio/feedback/Workaround.aspx?FeedbackID=98504
			// The MouseClick event handler for the grid is also used.

			sourcesDataGridView.EditMode = DataGridViewEditMode.EditOnEnter;
		}

		private void sourcesDataGridView_MouseClick(object sender, MouseEventArgs e)
		{
			// See the grid's CellEnter event handler for more information about the purpose of this handler.

			DataGridView.HitTestInfo hit = sourcesDataGridView.HitTest(e.X, e.Y);

			if (hit.Type == DataGridViewHitTestType.RowHeader)
			{
				sourcesDataGridView.EditMode = DataGridViewEditMode.EditOnKeystrokeOrF2;
				sourcesDataGridView.EndEdit();
			}
		}

		private void sourcesDataGridView_KeyUp(object sender, KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Escape && sourcesDataGridView.CurrentRow.IsNewRow)
				sourcesDataGridView.CancelEdit();
		}

		private void sourcesDataGridView_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
		{
			DataGridViewCell cell = sourcesDataGridView[e.ColumnIndex, e.RowIndex];

			string value = cell.Value as string;

			if (!string.IsNullOrEmpty(value))
			{
				cell.Value = new SystemPath(project.Directory, value, true).CompletePath;
			}
		}

		private void sourcesDataGridView_CellEndEdit(object sender, DataGridViewCellEventArgs e)
		{
			FindRelatedValue(e.RowIndex, e.ColumnIndex);

			DataGridViewCell cell = sourcesDataGridView[e.ColumnIndex, e.RowIndex];
			cell.ErrorText = null;

			string value = cell.Value as string;

			if (!string.IsNullOrEmpty(value) && System.IO.Path.IsPathRooted(value))
			{
				cell.Value = new SystemPath(value, true).CompleteRelativeTo(project.Directory);
			}
		}
		#endregion
	}
}